package org.ihelpuoo.core;

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.ihelpuoo.io.FileUtils;
import org.ihelpuoo.lang.ConfigException;
import org.ihelpuoo.lang.InitializeException;
import org.ihelpuoo.lang.NutException;
import org.ihelpuoo.util.Properties;

public class Nut
{

	private static Nut	org_ihelpuoo_core_nut	= null;

	public static Nut me()
	{
		if (org_ihelpuoo_core_nut == null)
		{
			synchronized (Nut.class)
			{
				if (org_ihelpuoo_core_nut == null)
				{
					org_ihelpuoo_core_nut = new Nut();
				}
			}
		}
		return org_ihelpuoo_core_nut;
	}

	public static Nut me(Map<Object, Object> map)
	{
		Nut nut = (Nut)map.get(Nut.class);
		if (null == nut)
		{
			synchronized (Nut.class)
			{
				nut = (Nut)map.get(Nut.class);
				if (null == nut)
				{
					nut = new Nut();
					nut.hostObject = map;
					map.put(Nut.class,nut);
				}
			}
		}
		return nut;
	}

	public static Nut me(ServletContext context)
	{
		Nut nut = (Nut)context.getAttribute(Nut.class.getName());
		if (null == nut)
		{
			synchronized (Nut.class)
			{
				nut = (Nut)context.getAttribute(Nut.class.getName());
				if (null == nut)
				{
					nut = new Nut();
					nut.hostObject = context;
					context.setAttribute(Nut.class.getName(),nut);
				}
			}
		}
		return nut;
	}

	public static Nut me(HttpSession session)
	{
		Nut nut = (Nut)session.getAttribute(Nut.class.getName());
		if (null == nut)
		{
			synchronized (Nut.class)
			{
				nut = (Nut)session.getAttribute(Nut.class.getName());
				if (null == nut)
				{
					nut = new Nut();
					nut.hostObject = session;
					session.setAttribute(Nut.class.getName(),nut);
				}
			}
		}
		return nut;
	}

	public static Nut me(ServletRequest request)
	{
		Nut nut = (Nut)request.getAttribute(Nut.class.getName());
		if (null == nut)
		{
			synchronized (Nut.class)
			{
				nut = (Nut)request.getAttribute(Nut.class.getName());
				if (null == nut)
				{
					nut = new Nut();
					nut.hostObject = request;
					request.setAttribute(Nut.class.getName(),nut);
				}
			}
		}
		return nut;
	}

	private Map<Class, Object>			objects;
	private Map<String, Properties>		confs;
	private Map<Properties, Factory>	factories;
	private Object						hostObject;

	private Nut()
	{
		confs = new HashMap<String, Properties>();
		factories = new HashMap<Properties, Factory>();
		objects = new HashMap<Class, Object>();
	}

	public Object getHostObject()
	{
		return hostObject;
	}

	public ServletContext getHostServletContext()
	{
		if (null == hostObject)
			return null;
		if (hostObject instanceof ServletContext)
			return (ServletContext)hostObject;
		if (hostObject instanceof HttpSession)
			return ((HttpSession)hostObject).getServletContext();
		if (hostObject instanceof HttpServletRequest)
			return ((HttpServletRequest)hostObject).getSession().getServletContext();
		return null;
	}

	public Object getObject(String className) throws ClassNotFoundException,
			InstantiationException, IllegalAccessException
	{
		return getObject(Class.forName(className));
	}

	public Object getObject(Class klass) throws InstantiationException, IllegalAccessException
	{
		Object obj = objects.get(klass);
		if (null == obj)
		{
			synchronized (objects)
			{
				obj = objects.get(klass);
				if (null == obj)
				{
					obj = klass.newInstance();
					objects.put(klass,obj);
				}
			}
		}
		return obj;
	}

	public void removeObject(String className)
	{
		try
		{
			removeObject(Class.forName(className));
		}
		catch (ClassNotFoundException e)
		{
			new RuntimeException(e);
		}
	}

	public void removeObject(Class klass)
	{
		synchronized (objects)
		{
			objects.remove(klass);
		}
	}

	public boolean hasObject(String className)
	{
		try
		{
			return hasObject(Class.forName(className));
		}
		catch (ClassNotFoundException e)
		{
			return false;
		}
	}

	public boolean hasObject(Class klass)
	{
		return objects.containsKey(klass);
	}

	public synchronized void addConfig(String key, Properties v)
	{
		confs.put(key,v);
	}

	/**
	 * It will finding your config from from JVM's class path.
	 * 
	 * @param fileName: the fileName of you config file
	 * @return ConfigInfo Object
	 * @throws InitializeException 
	 * @see ConfigProperties Class
	 */
	public Properties getConfig(String fileName) throws InitializeException
	{

		Properties config = (Properties)confs.get(fileName);
		if (null == config)
		{
			synchronized (confs)
			{
				config = (Properties)confs.get(fileName);
				if (null == config)
				{
					config = new Properties();
					try
					{
						File f = FileUtils.findFile(fileName);
						config.load(new FileInputStream(f));

					}
					catch (Exception e)
					{
						throw new InitializeException("Fail to load \"" + fileName
								+ "\" in classpath:\n" + System.getProperty("sun.boot.class.path")
								+ "!",e);
					}
					confs.put(fileName,config);
				}
			}
		}
		return config;
	}

	public void removeConfig(String fileName)
	{
		synchronized (confs)
		{
			confs.remove(fileName);
		}
	}

	public boolean hasConfig(String key)
	{
		return confs.containsKey(key);
	}

	public Factory getFactory(String fileName) throws NutException
	{
		return getFactory(getConfig(fileName));
	}

	public void removeFactory(String key) throws NutException
	{
		if (confs.containsKey(key))
			removeFactory(confs.get(key));
	}

	/**
	 * @param config
	 * @return
	 * @throws ConfigException 
	 * @throws InitializeException 
	 * @throws InitializeException 
	 */
	public Factory getFactory(Properties config) throws NutException
	{

		Factory factory = (Factory)factories.get(config);
		if (null == factory)
		{
			synchronized (factories)
			{
				factory = (Factory)factories.get(config);
				if (null == factory)
				{
					String className = config.getString("class");
					try
					{
						factory = (Factory)Class.forName(className).newInstance();
					}
					catch (Exception e)
					{
						throw new InitializeException(e);
					}
					factory.setNut(this);
					factory.init(config);
					factories.put(config,factory);
				}
			}
		}
		return factory;
	}

	public void removeFactory(Properties key) throws NutException
	{
		synchronized (factories)
		{
			factories.remove(key).depose();
		}
	}

	public boolean hasFactory(String fileName)
	{
		try
		{
			return hasFactory(getConfig(fileName));
		}
		catch (InitializeException e)
		{
			throw new RuntimeException(e);
		}
	}

	public void depose() throws NutException
	{
		Iterator<Factory> it = factories.values().iterator();
		while (it.hasNext())
			it.next().depose();
	}

	public boolean hasFactory(Properties key)
	{
		return factories.containsKey(key);
	}

}
